iT邦幫忙

2023 iThome 鐵人賽

DAY 8
0
Software Development

Haskell 從入門到放棄系列 第 8

[Haskell 從入門到放棄] Day 08 - Pattern Matching (2)

  • 分享至 

  • xImage
  •  

今天我們將繼續了解 pattern matching 的語法。

Guard

昨天只講到 pattern 如果是「特定值」那我們做什麼行為,但如果我們是有多個參數要一起參與判斷式的組成,那感覺就不太夠用了。

所以 Haskell 為我們提供了 Guard ,我們先來寫了一個 function 來判斷三個邊長是否可以構成一個合法的三角型

calcTriangle :: Int -> Int -> Int -> String
calcTriangle a b c
    | a + b <= c || a + c <= b || b + c <= a = "Invalid"
    | a == b && b == c = "Equilateral"
    | a == b || b == c || a == c = "Isosceles"
    | otherwise = "Scalene"

Guard 的用法很簡單,只要用 | 並在後面加上一個最後會回傳 Bool 的 expression 以及match到後要回傳的 expression ,然後依序從上到下如果失敗就會往下繼續 match 。

首先我們用三角形任意兩邊一定大於第三邊來當作判斷,如果成立為 True 就會回傳 "Invalid" ,如果不是就會接到下一個 guard ,接下來我們來判斷如果三邊相等那就代表他為正三角形所以回傳 "Equilateral" ,如果還不是就會繼續到下一個 guard ,以此類推直到最後的 otherwise

otherwise 就是 True ,意思就是一定會執行右邊的 expression ,通常就是放在最後面有點類似其他語言的 switch casedefault 之類的用途。但如果沒有提供 otherwise 且最後沒有匹配到任何一個 guard ,那跟一般的 pattern matching 一樣最後會拋出錯誤。

Where

上面的程式感覺還是有點羅嗦,有什麼辦法可以簡化重複的 pattern 嗎?這時候我們可以使用 where 幫定義我們需要重複的 expression。

calcTriangle' :: Int -> Int -> Int -> String
calcTriangle' a b c
    | a + b <= c || a + c <= b || b + c <= a = "Invalid"
    | abEq && bcEq = "Equilateral"
    | abEq || bcEq || acEq = "Isosceles"
    | otherwise = "Scalene"
    where
        abEq = a == b
        bcEq = b == c
        acEq = a == c

where 的用法就是放在 | 的後面然後定義名稱及值 ,所以我們可以 a == b 等等的布林運算定義成另外一個名稱,然後原本的 | a == b && b == c = "Equilateral" 就可以變成 | abEq && bcEq = "Equilateral" 讓我們在使用 pattern matching 時增加整體的可讀性,且 where 中所綁定的名稱只在這個 function 也就是 calcTriangle' 裡才有用。

除了綁定數值以外,where 也可以用來綁定 function

calcTriangle'' :: Int -> Int -> Int -> String
calcTriangle'' a b c
    | isValid a b c || isValid a c b || isValid b c a= "Invalid"
    | abEq && bcEq = "Equilateral"
    | abEq || bcEq || acEq = "Isosceles"
    | otherwise = "Scalene"
    where
        abEq = a == b
        bcEq = b == c
        acEq = a == c
        isValid x y z = x + y <= z

我們可以把 a + b <= c 這類的運算改用 where 來定義成一個 fucntion ,雖然在這裡看不出有太多的差異或者有什麼理由一定要這樣寫,但假設我們今天有一個很小的 function 想要抽出原本的 expression 但又不需要寫成全域的 function 我們也可以用 where 來協助我們達成這件事情。

拿我們前幾天的 List comprehension 的例子舉例:

[email | email <- emailList, elem '@' email && elem '.' email ] 

原本我們有一個 List comprehension 然後他的限制條件是判斷 email 裡有無 @. 但假設我們想把這個判斷抽出來也可以使用 where 來達成

filterValidEmail :: [String] -> [String]
filterValidEmail xs = 
    [email | email <- xs,  isValid email ]
    where 
        isValid email = '@' `elem` email &&  '.' `elem` email

這裡可以看到我們使用 where 來將 isValid 的邏輯抽出來,然後我再把原本的限制條件改為使用 isValid

總結來說 where 有點像是局部變數的概念,我們可以將某些數值或者 function 固定在某個 scope 有作用而已。

雖然沒有特別提到,但在 Haskell 中 function 也是一種變數。

Let

說到區域變數,也許有人會疑惑那跟 let 又差在哪裡?

我們先把上面的程式碼改為用 let in

filterValidEmail' :: [String] -> [String]
filterValidEmail' xs =
  let
    isValid email = '@' `elem` email && '.' `elem` email
  in
    [email | email <- xs, isValid email]

跟之前所介紹的一樣 let 綁定的名稱只在 in 裡面有作用,letwhere 的根本差異是 in 後面是接一個 expression 且 let 可以在任何地方使用

-- ghci 
foo = let a = 200 in a +1
foo -- 201

foo = [let a = 200 in a+1 , 202]
foo -- [201,202]

那至於該選擇 let 還是 where 呢?只能說看習慣與場合,但多數情況**「我個人認為」** where 比較好讀一點點

Case

case 使用其實就真的很像其他語言的 switch case 一樣 ,我們 case 一個值然後根據他的值而執行什麼。

sumList :: Num a => [a] -> a
sumList [] = 0
sumList (x:xs) = x + sumList xs

sumList' :: Num a => [a] -> a
sumList' list = case list of
    [] -> 0
    (x:xs) -> x + sumList' xs

那這樣 sumListsumList' 有什麼差?答案就是「沒差」,我們在 function 使用的 pattern matching 就只是 case 的語法糖而已。

case 看的出來也是一種 expression 所以我們一樣能在任何地方使用

foo x =
    [case x of
        'a' -> 10
        'b' -> 11
        'c' -> 12
        'd' -> 13
        'e' -> 14
        'f' -> 15
        _ -> error "Invalid hex digit"
    ,1]

今天大致上講到了大部分 pattern matching 的用法及特性了,相信各位應該對於 Haskell 的認識又更深了一層。

想起之前與同事有討論過 pattern matching (沒有特指哪一個的語言實作)跟 js/ts 的 switch case 差在哪裡,最直觀的差異是一個通常是 expression 而一個是 statement ,這個差異就就讓我們在使用場景差很多了,
因為只要是 expression 我可以大部分地方使用並取得我們要的值,但如果是 statement 我們可能需要用一個 function 包裝起來最後回傳一個值或者是一個 mutable variable 在各個 case 中間去更新值。

以及 pattern matching 不只是匹配 「值」 而已,而是連 「結構」 都可以匹配,以及匹配對象(或者該說流程的分支)的彈性都強大許多。也不難想像會什麼會有ts-pattern這個 library 的出現,畢竟 pattern matching 的好,用過就知道。


今天的程式碼:https://github.com/toddLiao469469/30days-for-haskell


上一篇
[Haskell 從入門到放棄] Day 07 - Pattern Matching
下一篇
[Haskell 從入門到放棄] Day 09 - 終於來到 higher order function
系列文
Haskell 從入門到放棄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
yaoooo
iT邦新手 5 級 ‧ 2023-09-23 22:20:24

一班一般

thanks~

我要留言

立即登入留言